﻿using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Security.Cryptography;

namespace WebArchiveCopier.IO
{
	public class FileDataManager : IDataManager
	{
		private string directory;
		private MD5 md5;

		public FileDataManager(string directory)
		{
			this.directory = directory;
			md5 = System.Security.Cryptography.MD5.Create();
		}

		public byte[] GetData(string address, string originalAddress, DateTime date, bool skipIfPresent)
		{
			string path = GetCachedFilePath(address, originalAddress, date);
			string directoryName = Path.GetDirectoryName(path);
			byte [] data = null;
			if (!File.Exists(path))
			{
				if (!Directory.Exists(directoryName))
				{
					Directory.CreateDirectory(directoryName);
				}

				AdvancedWebClient client = new AdvancedWebClient();
				try
				{
					data = client.DownloadData(address);
					File.WriteAllBytes(path, data);
				}
				catch (WebException e)
				{
					Console.WriteLine(e.Message);
				}
				Console.WriteLine(string.Format("\t {0} download", date.ToString("yyyy-MM-dd HH:mm:ss")));
			}
			else if (!skipIfPresent)
			{
				data = File.ReadAllBytes(path);
			}
			else
			{
				return new byte[0];
			}

			string metaDataFilename = Path.Combine(directoryName, "metadata.txt");
			if (!File.Exists(metaDataFilename))
			{
				using (StreamWriter fs = new StreamWriter(metaDataFilename, false))
				{
					fs.WriteLine(string.Format("uri: {0}", address));
					fs.WriteLine(string.Format("original uri: {0}", originalAddress));
					fs.WriteLine(string.Format("copy date: {0}", date.ToString("yyyy-MM-dd HH:mm:ss")));
				}
			}

			return data;
		}

		public byte[] GetData(string address, DateTime date)
		{
			return GetData(address, null, date, false);
		}

		public byte[] GetData(string address)
		{
			return GetData(address, DateTime.MinValue);
		}

		private string GetCachedFilePath(string address, string originalAddress, DateTime date)
		{
			string host;
			string directoryHash;

			Uri uri = new UriBuilder(address).Uri;

			string lastSegment = uri.Segments[uri.Segments.Length - 1];
			string filename = lastSegment == "/" ? "index_assumed.html" : lastSegment;
			string dateFormat = "yyyy-MM-dd HH_mm_ss";

			string [] segments = new string[uri.Segments.Length - 1];
			Array.Copy(uri.Segments, 0, segments, 0, uri.Segments.Length - 1);

			if (originalAddress != null)
			{
				Uri originalUri = new UriBuilder(originalAddress).Uri;
				host = originalUri.Host;
			}
			else
			{
				host = uri.Host;
			}

			int pathLength = directory.Length + host.Length + filename.Length * 2 + (date != DateTime.MinValue ? (dateFormat.Length + 1) : 0) + 32 + 4;

			if (filename.IndexOfAny(Path.GetInvalidFileNameChars()) != -1 || pathLength > 260)
			{
				filename = CalculateMD5(filename);
			}
			
			if (originalAddress != null && date != DateTime.MinValue)
			{
				directoryHash = CalculateMD5(string.Format("web/{0}id_/{1}", date.ToString("yyyMMddHHmmss"), originalAddress));
			}
			else
			{
				directoryHash = CalculateMD5(string.Join("/", segments));
			}

			string hostPath = Path.Combine(directory, host);
			string filenamePath = Path.Combine(hostPath, filename);
			if (date != DateTime.MinValue)
			{
				filenamePath = Path.Combine(filenamePath, date.ToString(dateFormat));
			}
			string hashPath = Path.Combine(filenamePath, directoryHash);
			string result = Path.Combine(hashPath, filename);
			try
			{
				Path.GetFullPath(result);
			}
			catch (PathTooLongException e)
			{
				throw new Exception(string.Format("Path too long: '{0}'.", result), e);
			}
			return result;
		}

		private string CalculateMD5(string input)
		{
			byte[] inputBytes = System.Text.Encoding.ASCII.GetBytes(input);
			byte[] hash = md5.ComputeHash(inputBytes);

			StringBuilder sb = new StringBuilder();
			for (int i = 0; i < hash.Length; i++)
			{
				sb.Append(hash[i].ToString("X2"));
			}
			return sb.ToString();
		}
	}
}
